/*******************************************************************************
 * Copyright (c) 2014 Francesco Gabbrielli
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors:
 *     Francesco Gabbrielli - initial API and implementation
 ******************************************************************************/
package it.francescogabbrielli.android.solidalapp;

import java.util.LinkedList;
import java.util.List;

import it.francescogabbrielli.android.solidalapp.SolidalApp.DataKey;
import it.francescogabbrielli.android.solidalapp.response.ResponseActivity;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockDialogFragment;
import com.actionbarsherlock.app.SherlockFragment;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.view.MenuItem;
import com.actionbarsherlock.view.Window;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;

/**
 * <p>
 * Base activity with action bar support and location listener implementation.
 * Every activity of the app should extends this class.
 * 
 * <p>
 * Every {@link BaseActivity} is also a listener for {@link StatusEvent}s and
 * {@link DataEvent}s but leaves implementation to subclasses. It registers 
 * itself for receiving such events to the application in {@link #onResume()} 
 * and unregisters in {@link #onPause()}, so every event that will be launched 
 * outside this life-cycle range will be ignored. To make sure a certain event
 * can be received by this activity, use {{@link #addOnResumeAction(Runnable)} 
 * for posting code that might fire that event so it can be executed in 
 * {@link #onResume()}
 */
public abstract class BaseActivity extends SherlockFragmentActivity 
implements
	GooglePlayServicesClient.ConnectionCallbacks,
	GooglePlayServicesClient.OnConnectionFailedListener,
	LocationListener, StatusListener, DataListener {

	
	/** Tell if resumed */
	private boolean resumed;
	
	/** List of actions to execute in {@link #onResume()} */
	private List<Runnable> resumeActions;
	
	/** Executor for resume actions */
	private SerialExecutor resumeExecutor;
	
	/** The app instance */
	protected SolidalApp app;

	/** Location client */
	protected LocationClient locationClient;
	
	/** New Intent flag */
	private boolean newIntent;
	
	public BaseActivity() {
	}
	
	/** Return the app instance */
	public SolidalApp getApp() {
		return app;
	}
	
	
	/**
	 * Add a tab to action bar
	 * 
	 * @param text
	 * 		tab text
	 * @param container
	 * 		parent container
	 * @param tag
	 * 		tag 
	 * @param fragmentClass
	 * 		the class whose fragment to instantiate
	 * @return 
	 * 		the tab
	 */
	protected Tab addTab(int text, int container, String tag, Class<? extends SherlockFragment> fragmentClass) {
		ActionBar actionBar = getSupportActionBar();
		TabListener l = new TabListener(this, container, tag, fragmentClass);
	    Tab tab = actionBar.newTab()
                .setText(text)
                .setTag(tag)
                .setTabListener(l);
	    actionBar.addTab(tab);
	    return tab;
	}	

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		
		requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);		

		super.onCreate(savedInstanceState);
		
//		Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this));
		
		app = (SolidalApp) getApplicationContext();
		
		locationClient = new LocationClient(this, this, this);
		
		setSupportProgressBarIndeterminate(true);
		
		Log.d(SolidalApp.TAG, "CREATE BaseActivity: "+getClass().getSimpleName());
		
	}
	
	/**
	 * Start "active" life-cycle" making a few checks:
	 * <ol>
	 * <li>
	 * Notify the application that this activity is started, and from now 
	 * on it can be retrieved by {@link SolidalApp#getCurrentActivity()}
	 * <li>
	 * Connect to the location client
	 * <li>
	 * Try to register to GCM and Cloud Endpoints 
	 * </ol> 
	 */
	@Override
	protected void onStart() {
		super.onStart();
		app.setCurrentActivity(this);
		locationClient.connect();
		app.registerToServices();
//		Log.d(SolidalApp.TAG, "START BaseActivity: "+getClass().getSimpleName());
	}

	/**
	 * <p>
	 * Register the activity to the application as a {@link StatusListener} and
	 * as {@link DataListener} so it can receive the corresponding events.
	 * 
	 * <p>
	 * Finally if there are actions added by {@link #addOnResumeAction(Runnable)} 
	 * they will be executed
	 */
	@Override
	protected void onResumeFragments() {
		super.onResumeFragments();
		app.addStatusListener(this);
		app.addDataListener(this);
		
		// fire generic events to "wake up" the activity
		if (!newIntent) {
			onStatusUpdate(new StatusEvent());
			for (DataKey key : DataKey.values())
				onDataChanged(new DataEvent(this, key));
		}
		newIntent = false;
		
		synchronized (this) {
			resumed = true;
			if (resumeActions!=null) {
				for (Runnable r : resumeActions)
					r.run();
				resumeActions.clear();
				resumeActions = null;
			}
		}
//		Log.d(SolidalApp.TAG, "RESUME BaseActivity: "+getClass().getSimpleName());
	}

	/**
	 * Unregister from application as {@link StatusListener} and {@link DataListener}
	 */
	@Override
	protected void onPause() {
		resumed = false;
		app.removeStatusListener(this);
		app.removeDataListener(this);
//		app.setActive(this, false);
//		Log.d(SolidalApp.TAG, "PAUSE BaseActivity: "+getClass().getSimpleName());
		super.onPause();
	}

	/**
	 * Close "active" life-cycle
	 * <ol>
	 * <li>
	 * Disconnect from location client
	 * <li>
	 * Notify the application that this activity stopped and will no longer
	 * be the "current activity"
	 * </ol>
	 */
	@Override
	protected void onStop() {
//		app.setActive(this, false);
		locationClient.disconnect();
//		Log.d(SolidalApp.TAG, "STOP BaseActivity: "+getClass().getSimpleName());
		super.onStop();
		app.removeCurrentActivity(this);
	}
	
	@Override
	protected void onDestroy() {

		super.onDestroy();
//		Log.d(SolidalApp.TAG, "DESTROY BaseActivity: "+getClass().getSimpleName());
	}
	
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case R.id.action_settings:
			startActivity(new Intent(this, SettingsActivity.class));
			return true;
		case R.id.action_help:
			String url = getString(R.string.help_url);
			Intent i = new Intent(Intent.ACTION_VIEW);
			i.setData(Uri.parse(url));
			startActivity(i);
			return true;
		case android.R.id.home:
			// This ID represents the Home or Up button. In the case of this
			// activity, the Up button is shown. Use NavUtils to allow users
			// to navigate up one level in the application structure. For
			// more details, see the Navigation pattern on Android Design:
			//
			// http://developer.android.com/design/patterns/navigation.html#up-vs-back
			//
			if((getSupportActionBar().getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP)!=0)
				NavUtils.navigateUpFromSameTask(this);
			return true;
		}
		return super.onOptionsItemSelected(item);
	}
	
	/**
	 * <p>
	 * Receive intents from singletop events
	 * 
	 * <p>
	 * <b>Attention:</b> make sure every action implemented in subclasses that 
	 * could fire a {@link StatusEvent} or a {@link DataEvent} before {@link #onResume()} 
	 * is wrapped in with {@link #addOnResumeAction(Runnable)}</b>
	 */
	@Override
	protected void onNewIntent(Intent intent) {
		
		super.onNewIntent(intent);
		
		// check if it is really a new intent
		int id = intent.getIntExtra(Constants.INTENT, 0);
		SharedPreferences prefs = getSharedPreferences(
				SolidalApp.class.getCanonicalName(), MODE_PRIVATE);
		synchronized (prefs) {
			if (id==0 || id>prefs.getInt(Constants.INTENT, 0)) {
				if (id>0)
					prefs.edit().putInt(Constants.INTENT, id).commit();
				newIntent = true;
				Log.i(SolidalApp.TAG, "CHIC New intent: "+EndpointsUtils.toString(intent.getExtras()));
			}
		}
		
		switch (GCMMessageReceiver.Action.valueOf(intent)) {
		case GCM_FEEDBACK_RECEIVED:
			// forward to response if not there...
			if(getClass()!=ResponseActivity.class) {
				intent.setClass(this, ResponseActivity.class);
				startActivity(intent);
			}
			break;
		default:
		}
		
	}
	
	@Override
	public void onConnected(Bundle data) {
		app.getStatus().setLocation(locationClient.getLastLocation());
	}
	
	@Override
	public void onDisconnected() {
		
	}
	
	@Override
	public void onConnectionFailed(ConnectionResult result) {
		Toast.makeText(this, "Positioning error " + result.getErrorCode(), Toast.LENGTH_SHORT).show();
	}
	
	@Override
	public void onLocationChanged(Location location) {
		app.getStatus().setLocation(location);
	}
			
//	@Override
//	public void onProviderDisabled(String provider) {
//		Log.d(SolidalApp.TAG, "Provider disabled: "+provider);
//	}
//	
//	@Override
//	public void onStatusChanged(String provider, int status, Bundle extras) {
//		Log.d(SolidalApp.TAG, "Provider changed: "+provider+" "+extras);
//	}
//	
//	@Override
//	public void onProviderEnabled(String provider) {
//		Log.d(SolidalApp.TAG, "Provider enabled: "+provider);
//	}
	
    /**
     * Check the device to make sure it has the Google Play Services APK. If
     * it doesn't, display a dialog that allows users to download the APK from
     * the Google Play Store or enable it in the device's system settings.
     */
    protected boolean checkPlayServices() {
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        if (resultCode != ConnectionResult.SUCCESS) {
            if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
                GooglePlayServicesUtil.getErrorDialog(resultCode, this,
                        Constants.PLAY_SERVICES_RESOLUTION_REQUEST).show();
            } else {
                Log.i(SolidalApp.TAG, "This device is not supported.");
                finish();
            }
            return false;
        }
        return true;
    }
    
    /**
     * Hide input keyboard
     */
    protected void hideKeyboard() {
		InputMethodManager inputManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
		View v = getCurrentFocus();
		if(v!=null)
			inputManager.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
    }
    
    /**
     * Redirect an intent coming from a notification to {@link #onNewIntent(Intent)} 
     * (this will happen only the first time that the notification is processed)
     */
	protected void forwardNotification() {
		
		// check
		Intent intent = checkNotification();
		
		int notification = intent.getIntExtra(Constants.NOTIFICATION, 0);
		SharedPreferences prefs = getSharedPreferences(SolidalApp.class.getCanonicalName(), MODE_PRIVATE);
		int last = prefs.getInt(Constants.LAST_NOTIFICATION, 0);

		// remove
		NotificationManager manager = 
		        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		manager.cancel(notification);
		
		Log.i(SolidalApp.TAG, "CHIC Notification: valid="+(notification>last));
		
		// forward if it's the last one
		if (notification>last) {
			synchronized (prefs) {
				prefs.edit().putInt(Constants.LAST_NOTIFICATION, notification).commit();
			}
			onNewIntent(intent);
//		} else {
//			intent.removeExtra("id");//XXX
		}

	}
	
	/**
	 * Check if the current intent is a (fresh) notification
	 * 
	 * @return
	 * 		the current intent, with {@link Constants#NOTIFICATION} set if it comes from notification
	 */
	protected Intent checkNotification() {
		Intent intent = getIntent();
		SharedPreferences prefs = getSharedPreferences(SolidalApp.class.getCanonicalName(), MODE_PRIVATE);
		int notification = intent.getIntExtra(Constants.NOTIFICATION, 0);
		if (prefs.getInt(Constants.LAST_NOTIFICATION, 0) >= notification)
			intent.removeExtra(Constants.NOTIFICATION);
		return intent;
	}
	
	/**
	 * Add code to be run in {@link #onResume()}
	 * 
	 * @param runnable
	 * 		the code
	 */
	protected synchronized void addOnResumeAction(Runnable runnable) {
		if (resumed) {
			if (resumeExecutor==null)
				resumeExecutor = new SerialExecutor();
			resumeExecutor.execute(runnable);
		} else {
			if (resumeActions==null)
				resumeActions = new LinkedList<Runnable>();
			resumeActions.add(runnable);
		}
	}
	
	/**
	 * Check location service and open dialog if none
	 */
	protected boolean checkLocationServices() {
		LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);
		boolean gps_enabled=false, network_enabled=false;
		try {
			gps_enabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
		} catch (Exception ex) {
		}
		try {
			network_enabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
		} catch (Exception ex) {
		}
	    if(!gps_enabled && !network_enabled) {
	    	new CheckLocationDialog().show(getSupportFragmentManager(), "check_location" );
	    	return true;
	    }
	    return false;
	}
	
	/** 
	 * Check location settings dialog
	 */
	protected class CheckLocationDialog extends SherlockDialogFragment {		
		
		@Override
		public Dialog onCreateDialog(Bundle savedInstanceState) {
			AlertDialog.Builder builder = new AlertDialog.Builder(BaseActivity.this)
				.setTitle(R.string.warning)
				.setMessage(R.string.check_location)
				.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						Intent viewIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
						startActivity(viewIntent);
						dialog.dismiss();
						locationClient.requestLocationUpdates(
								new LocationRequest()
									.setNumUpdates(1)
									.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY), 
								BaseActivity.this);
					}
				})
				.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						dialog.dismiss();
					}
				});
			
			return builder.create();
		}
	}
	
}
